package cc.rengu.oltp.utility.util;

import cc.rengu.oltp.utility.annotation.MAPPING;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * 自定义注解解析工具类
 * Created by 王成 on 2017/11/28.
 */
public class AnnotationUtil {

    /**
     * 获取对象中某个字段注解值
     *
     * @param clasz      对象类
     * @param fieldName  字段名
     * @param annotation 注解类
     * @return 字段注解值
     * @throws Exception 异常
     */
    public static String getFieldAnnotationValue(Class<?> clasz, String fieldName, Class<? extends Annotation> annotation) throws Exception {
        return getFieldAnnotationValue(clasz, fieldName, annotation, null);
    }

    /**
     * 获取对象类中某个字段的注解字段值
     *
     * @param clasz           对象类
     * @param fieldName       对象字段
     * @param annotation      注解类
     * @param annotationField 注解字段名
     * @return 注解值
     * @throws Exception 异常
     */
    public static String getFieldAnnotationValue(Class<?> clasz, String fieldName, Class<? extends Annotation> annotation, String annotationField) throws Exception {
        Field field = clasz.getDeclaredField(fieldName);
        if (null != field && field.isAnnotationPresent(annotation)) {
            Annotation[] annotations = field.getAnnotations();
            Optional<Annotation> annotationOpt = Arrays.stream(annotations).filter(item -> item.annotationType().getSimpleName().equals(annotation.getSimpleName())).findFirst();
            if (annotationOpt.isPresent()) {
                Method[] methods = annotationOpt.get().getClass().getDeclaredMethods();
                Optional<Method> annotationFieldMethodOpt = Arrays.stream(methods).filter(item -> item.getName().equals((null == annotationField) ? "value" : annotationField)).findFirst();
                if (annotationFieldMethodOpt.isPresent()) {
                    return (String) annotationFieldMethodOpt.get().invoke(annotationOpt.get());
                }
            }
        }
        return null;
    }

    /**
     * 获取对象中某个方法的注解值
     *
     * @param clasz      对象类
     * @param annotation 注销类
     * @param methodName 方法名
     * @param paramTypes 方法参数类型
     * @return 方法注解值
     * @throws Exception 异常
     */
    public static String getMethodAnnotationValue(Class<?> clasz, Class<? extends Annotation> annotation, String methodName, Class<?>... paramTypes) throws Exception {
        return getMethodAnnotationValue(clasz, annotation, null, methodName, paramTypes);
    }

    /**
     * 获取对象中某个方法的注解值
     *
     * @param clasz           对象类
     * @param annotation      注销类
     * @param annotationField 注解字段名
     * @param methodName      方法名
     * @param paramTypes      方法参数类型
     * @return 方法注解值
     * @throws Exception 异常
     */
    public static String getMethodAnnotationValue(Class<?> clasz, Class<? extends Annotation> annotation, String annotationField, String methodName, Class<?>... paramTypes) throws Exception {
        Method method = clasz.getDeclaredMethod(methodName, paramTypes);
        if (null != method && method.isAnnotationPresent(annotation)) {
            Annotation[] annotations = method.getAnnotations();
            Optional<Annotation> annotationOpt = Arrays.stream(annotations).filter(item -> item.annotationType().getSimpleName().equals(annotation.getSimpleName())).findFirst();
            if (annotationOpt.isPresent()) {
                Method[] methods = annotationOpt.get().getClass().getDeclaredMethods();
                Optional<Method> annotationFieldMethodOpt = Arrays.stream(methods).filter(item -> item.getName().equals((null == annotationField) ? "value" : annotationField)).findFirst();
                if (annotationFieldMethodOpt.isPresent()) {
                    return (String) annotationFieldMethodOpt.get().invoke(annotationOpt.get());
                }
            }
        }
        return null;
    }

    /**
     * 根据注解名查找包路径下面的类文件
     *
     * @param packageName 包路径
     * @param annoName    注解名称
     * @param annotation  注解类型
     * @return 类文件
     * @throws Exception 异常
     */
    public static Class<?> findPackageClass(String packageName, String annoName, Class<? extends Annotation> annotation) throws Exception {
        List<Class<?>> classList = getPackageClassList(packageName, annotation);
        for (Class clazz : classList) {
            MAPPING classAnnoName = (MAPPING) clazz.getAnnotation(annotation);
            if (null != classAnnoName && classAnnoName.value().equals(annoName)) {
                return clazz;
            }
        }
        return null;
    }

    /**
     * 获取包路径下指定的注解类型的类文件
     *
     * @param packageName 包路径名
     * @param annotation  注解类型
     * @return 类文件集合
     * @throws Exception 异常
     */
    private static List<Class<?>> getPackageClassList(String packageName, Class<? extends Annotation> annotation) throws Exception {
        List<Class<?>> classList = new ArrayList<>();
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            /* 获取当前URL的类型 */
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                filePath = filePath.substring(1);
                getFilePathClasses(packageName, filePath, classList, annotation);
            }
        }
        return classList;
    }

    /**
     * 从指定的包下面找到文件名
     *
     * @param packageName 包路径
     * @param filePath    文件路径
     * @param classList   类文件集合
     * @param annotation  注解类型
     * @throws Exception 异常
     */
    private static void getFilePathClasses(String packageName, String filePath, List<Class<?>> classList, Class<? extends Annotation> annotation) throws Exception {
        Path dir = Paths.get(filePath);
        /* 获取当前目录下文件的stream流 */
        DirectoryStream<Path> stream = Files.newDirectoryStream(dir);
        for (Path path : stream) {
            String fileName = String.valueOf(path.getFileName());
            String className = fileName.substring(0, fileName.length() - 6);
            Class<?> classes = Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className);
            /* 判断该注解类型是不是所需要的类型 */
            if (null != classes && null != classes.getAnnotation(annotation)) {
                /* 把这个文件加入classlist中 */
                classList.add(classes);
            }
        }
    }
}
